🤖 feat: Agent Plugins install/update UX (managed installs, v1) - #3820
🤖 feat: Agent Plugins install/update UX (managed installs, v1)#3820ThomasK33 wants to merge 24 commits into
Conversation
New AgentPluginInstallEntry schema persisted as a 'plugins' section in ~/.mux/config.json via Config's atomic writes. source.ref is the tracking channel; lockedSha is what runs. Invalid entries are dropped lenient-on-read (discovery stays the source of truth for what loads).
- discoverAgentPluginAt: public single-root discovery wrapper so the installer validates staged clones with the exact runtime validation - extract normalizeRepoUrlForClone into src/node/utils/gitUrls.ts (shared with the project clone flow) - sourceInput grammar: owner/repo[/subpath][@ref] shorthand + URL passthrough - AgentPluginInstallService: stateless preview (temp shallow clone, consent payload with skills + MCP command lines), exact-SHA install with rollback, list (managed + unmanaged + missing), uninstall (override pruning, optional plugin-data purge), ls-remote update checks, swap-based update - MCPServerManager.stopServersWithKeyPrefix: explicit plugin-server recycle - plugins.* oRPC endpoints gated on the agent-plugins experiment
- PluginsSettingsSection: managed/unmanaged/missing list with update badges, Check for updates (on section open + explicit button only), two-phase Add flow (source input → consent preview listing every skill and MCP command line → install), inline uninstall confirm with unchecked-by-default 'also delete stored plugin data' checkbox - gated on the agent-plugins experiment (section, redirect, palette entry) - mock oRPC client support + Storybook stories with play assertions
Also fix lint (unsafe any in registry normalization) and prettier.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3b9d7245ce
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Registry moved to standalone ~/.mux/plugins.json: older builds rebuild config.json from known fields on save (passthrough only affects schema validation), so a downgrade would drop an embedded registry; owning the file also lets writes THROW so install rolls back the promoted dir and uninstall/update surface persistence failures instead of silently succeeding (Q2's own contingency: 'migration to a separate file') - Traversal safety: plugin-name grammar (shared via src/common/utils/agentPluginName.ts) enforced in the registry schema and asserted in targetPathFor, so a malformed entry named '.'/'..' can never resolve outside the container that uninstall deletes recursively - Fallback clone: reset the staging dir before the branch-clone fallback (fetchExactSha leaves an initialized repo; git clone refuses non-empty) - Keyboard rule: palette commands Install Agent Plugin… (opens the section with the add panel expanded), Check for Plugin Updates (toast + navigate), Update All Plugins (applies update-available; moved tags stay manual) - New tests: registry survives config.json rewrites, traversal names dropped, registry-write failure rollback, SHA-fetch-refused fallback (file:// remote with uploadpack.allowAnySHA1InWant=false)
|
Addressed all five Codex findings in b9c9a10:
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b9c9a1062a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- uninstall: stage the tree out (rename to staging) BEFORE the registry write; a locked/undeletable tree now fails cleanly with the install fully intact, and a failed registry write renames the tree back - palette 'Install Agent Plugin…': mounted sections subscribe to the intent so invoking the command while Settings → Plugins is already open expands the add panel (initializer covers the fresh-mount path) - section: mutation errors are re-asserted after refresh (refresh success cleared them); failed uninstall keeps the confirmation open - palette check/update-all: per-plugin status:'error' entries no longer read as 'all up to date' — surface which plugins failed and navigate
|
Addressed all four round-2 findings in edcdfa0:
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: edcdfa05fe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- palette 'Uninstall Agent Plugin…' with a managed-plugin select prompt; routes through the section's confirmation flow (plugin-data checkbox, destructive button) — the palette never uninstalls directly - pluginsSectionIntents generalized to a typed intent bus (open-add-panel / confirm-uninstall / refresh); mounted sections subscribe, unmounted sections consume the buffered intent on mount - 'Update All Plugins' publishes a refresh intent so an already-mounted section re-queries instead of showing stale versions/badges - sourceInput expands ~/-relative local paths (git is spawned without a shell, so ~ never expands on its own)
|
Addressed all three round-3 findings:
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a6503d2fe4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- update: stop the plugin's MCP servers BEFORE the old tree is renamed (live servers can lose files mid-swap on POSIX; open handles can fail the rename on Windows), and recycle again post-promote so content changed behind an unchanged command line still restarts — regression test snapshots the installed tree version at each recycle (pre-swap sees v1, post sees v2) - uninstall: best-effort trash deletion (catch + log + leave for staging reclamation) so an undeletable staged tree cannot abort override pruning; update's replaced-tree deletion gets the same treatment — regression test forces EBUSY and verifies uninstall completes and reinstall works - palette Update All / Check for Updates: check failures stay in the final summary even when other updates succeeded (mixed results toast as errors) - section: update-check errors live in separate state from list/mutation errors, so the concurrent mount refresh can never clear an unreachable- remote warning (rendered as its own banner) - sourceInput: expand ~\-style Windows home paths, not just ~/
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc68e771d7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- P1 MCP startup race: stopServersWithKeyPrefix records an epoch-stamped prefix invalidation; getToolsForWorkspace snapshots the clock before reading config and closes matching instances at every publish point (fresh start, timed-out retry, mid-stream restart) instead of publishing them — an update/uninstall swap during an in-flight startup can no longer leave an old-tree server running. Race test gates startServers, swaps mid-flight, and asserts the instance is closed, not published - registry rewrites are raw-preserving: mutations operate on the raw entry list (per-element validation on read, matched by name on write), so entries/fields from newer builds survive install/update/uninstall on this build; lifecycle test seeds an archive-source entry + unknown field - managed list rows keep registry identity (update/uninstall look up by it); manifest-name drift is surfaced in the description instead of breaking repair from Settings - update revalidates the resolved ref kind before cloning (deleted branch replaced by same-name tag → clear error, registry untouched) - palette Check for Plugin Updates publishes a refresh intent so a mounted section's badges match the toast - pinned phone-viewport story variant (Pixel matrix + mobile1 global) for the narrow-width row layout
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a2bd105f4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- P1: instances removed by mid-startup invalidation are queued in the timed-out retry list at every publish point — the entry is published under the unchanged full config signature, so without a retry marker the cached path would serve the reduced map and the updated plugin's tools would stay unavailable indefinitely; the race test now asserts the subsequent getToolsForWorkspace restarts the server from the new tree - P2: stopServersWithKeyPrefix closes ONLY matching instances, preserving the rest of the workspace cache (an unrelated healthy client is no longer torn down under a live lease/mid tool call); removed keys go through the same retry markers; test covers two servers + a held lease
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0443e1af3e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- P1 uninstall re-invalidation: issue a second stopServersWithKeyPrefix AFTER the tree+registry removal (mirroring update's post-promote stop) so a startup that snapshotted the new epoch but discovered the plugin before the rename cannot keep a server running from the removed tree — test asserts the recycle pair observes tree-present then tree-gone - P1 remove manual useCallback: PluginsSettingsSection handlers are plain functions (React Compiler repo); mount/subscription effects key on [api] with documented eslint-disable per repo precedent - P2 nested raw preservation: update patches only owned fields (lockedSha, updatedAt, manifest version/description) into the RAW entry instead of spreading the Zod-parsed entry — unknown metadata inside source/manifest survives downgrade round-trips (tested) - P2 stale update-check responses: generation counter; only the latest check commits updateChecks/checkingUpdates state - P2 plugin-data staging: when deletion is requested, the data dir is staged out BEFORE the registry commit; failure aborts the uninstall with the row intact so the cleanup can be retried (tested), rollback restores both tree and data on registry-write failure
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6965bc298
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Update All: moved tags join the final summary ('Tag moved for X — review
in Settings → Plugins') and taint the toast; tag-moved-only results no
longer report 'All plugins are up to date'
- Update All: the refresh intent publishes before any early return, so a
mounted section picks up newly discovered moved tags / check errors even
when no branch update applied; tag-moved / check-failure outcomes also
navigate to the section
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e576538423
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
uninstall runs the post-commit MCP invalidation BEFORE override pruning: pruneWorkspaceOverrides can throw from getAllWorkspaceMetadata (outside its per-workspace catch), and a pruning failure must not skip the invalidation that catches servers started from the removed tree mid-uninstall (test: metadata enumeration rejects → both stops ran, uninstall committed)
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8581764548
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- refresh() gets the same request-generation guard as checkForUpdates: an older overlapping list response can no longer resurrect removed rows or stale versions over a newer refresh - uninstall enumerates its override-pruning targets (workspace metadata) BEFORE committing anything: enumeration is the only pruning step that can fail wholesale, and post-commit it would strand stale enabled-server overrides with no Settings row to retry from (reinstall reuses the same instance ID and would silently re-enable servers); per-workspace pruning stays best-effort post-commit (test: enumeration failure aborts fully intact with zero stops, retry completes with both invalidations)
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a22a9e0cc3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
failed per-workspace override prunes persist a retryable tombstone (pendingOverridePrunes in the plugins.json envelope): retried on section open (list), and a reinstall of the same name is hard-gated on the pending prune for its instance-ID prefix — retry-then-refuse, so a stale enabledServers key can never silently re-enable a reinstalled plugin's server. Test: unavailable checkout → uninstall commits with tombstone, reinstall refuses; checkout recovers → override pruned, tombstone cleared, reinstall succeeds
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09fbbc29e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- P1: retryPendingOverridePrunes runs its read-modify-write under the exclusive mutation queue — a section-open retry can no longer clobber a concurrent install/update/uninstall with its stale registry snapshot (race test: gated prune during list + concurrent install → entry survives, tombstone cleared) - P2: the uninstall COMMIT write carries a pessimistic tombstone for every workspace to prune; the post-prune write only shrinks it (best-effort), so losing that write leaves the safe over-broad record instead of no record (test: prune + shrink-write both fail → tombstone durable, reinstall gated) - P2: tombstone retries reconcile workspace IDs against current metadata and drop deleted workspaces — a permanently-missing checkout can no longer block reinstall forever (test: dead-ID tombstone → reinstall succeeds, tombstone retired)
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d005e8c877
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- WorkspaceMcpOverridesService.removeOverridesFile checks the rm exit code and throws: setOverridesForWorkspace must reject when clearing overrides fails, or the tombstone machinery would classify the prune as successful while the stale enabledServers key survives - tombstone rewrites are raw-preserving: unrecognized tombstone variants pass through verbatim and recognized items keep unknown fields when their workspaceIds shrink (test: future variant + extra field survive a full uninstall cycle) - the post-uninstall tombstone shrink re-reads the registry STRICT inside a try/catch: a lenient read degrading transient corruption to an empty document would have rewritten plugins.json with an empty plugin list; on failure the pessimistic commit-write tombstone simply stays
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 28d8bc6d0e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Consent preview now resolves symlinked skill dirs with allowMissing containment (matching runtime assertSkillDirValid), so escaping symlinks surface a warning instead of hiding behind ENOENT while in-root symlinked skills are disclosed. - Plugin location/source lines wrap with break-all so max-length separator-free names cannot overflow the card at phone widths; pinned phone story covers a 64-char name with a scroll-width assertion.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c62f9345a3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Stale Workspace MCP dialog snapshots could restore plugin:<instanceId>:
override keys that an uninstall had just pruned, silently re-enabling a
reinstalled plugin's MCP server.
- workspace.mcp.get now returns { overrides, revision } where revision
is a content hash of the normalized overrides.
- workspace.mcp.set requires expectedRevision and rejects with a
conflict error when the stored overrides changed since that read;
saves are serialized through a write queue so check-and-set is atomic.
- WorkspaceMCPModal passes the loaded snapshot's revision on save, so a
stale save surfaces 'settings changed while this dialog was open'
instead of clobbering the prune.
- The uninstaller's override prune passes expectedRevision too and
re-reads + re-filters on conflict (bounded retries), so a concurrent
dialog save cannot interleave with the prune's read-modify-write.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
mux/src/node/services/mcpServerManager.ts
Line 1445 in a7e59c1
Fresh evidence after the epoch fix is the await between the final epoch scan and this cache publication. If an update/uninstall's post-swap stopServersWithKeyPrefix continuation runs during that yield, it records the new epoch and scans before this entry exists; this continuation then publishes a server that discovered the old tree without rechecking the epoch, allowing an uninstalled or replaced plugin process to remain active. Recheck synchronously at publication or otherwise serialize the check-and-publish step with prefix invalidation.
AGENTS.md reference: AGENTS.md:L149-L149
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
An await separated the final prefix-invalidation epoch scan from cache publication. A stopServersWithKeyPrefix continuation scheduled into that microtask yield records its epoch AFTER the scan checked it and scans the published map BEFORE workspaceServers.set runs — both mechanisms miss, and a server started from a removed/replaced plugin tree stays alive without a retry marker. closeInvalidatedInstancesThenPublish now re-scans until the invalidation clock is stable across a full scan and invokes the publish callback synchronously in the same continuation as the final clock check. Any invalidation landing after that check runs its own published-map scan strictly after publication (single-threaded), so it sees the entry and closes matching instances. Applied at all three publication sites (miss path, cached retry path, deferred restart path). Regression test interleaves stopServersWithKeyPrefix into the exact yield window via a one-shot instances-map iterator hook that queues a microtask; it fails on the previous code (stale instance published, close never called) and passes now.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9e9c94e2b8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Round 20 review: both findings are P2 hardening extensions of the already-landed race fixes (rounds 18/19). Per maintainer direction, the P1 scope for this PR is complete; these two are recorded here as follow-up-PR items rather than fixed in this PR, and their threads are resolved with this comment as the response. Follow-up 1 — Discard stale workspace MCP load results (
|
Summary
v1 of the Agent Plugins install/update UX ("Option B: managed installs"): paste a git URL or
owner/repo[@ref]into Settings → Plugins, get a consent preview of everything the plugin contributes (manifest, every skill, every MCP server command line), and install into~/.mux/pluginswith provenance recorded in a managed-install registry. Update badge + manual update, uninstall with override pruning, all behind the existingagent-pluginsexperiment.Background
PR #3815 shipped Agent Plugins 1.0.0 as discovery-only: users had to
git cloneinto container dirs by hand, with no provenance, no update signal, no uninstall, and no list surface. The design doc (docs/research/agent-plugin-integration-options.mdon branchresearch-agent-plugin-ux) compared five options; Thomas signed off on Option B (managed installs) with the §6 proposed decisions ratified.Approved decisions implemented here
~/.mux/plugins.jsonowned by the install service (atomic, throwing writes; in-process serialized mutations). Lenient-on-read: invalid entries are dropped with a warning, and plugin names are pattern-validated so a malformed entry can never resolve a path outside the container. (Deviation from §6-Q2's letter, following its own contingency: Codex review demonstrated that.passthrough()only affects schema validation — older builds rebuild config.json from known fields on save, so a downgrade would drop an embedded registry section. Q2 priced exactly this: "Cost if wrong: a one-time migration to a separate file." A file older builds never rewrite is the only mechanism that actually survives downgrade round-trips, and owning the write path also makes registry-persistence failures observable for rollback.)source.refis the tracking channel,lockedShais what runs. No ref given ⇒ record the remote default branch + pin its current SHA. Tag/SHA refs are pinned (moved tags surface atag movedwarning badge). Nothing auto-applies, ever.~/.mux/plugin-staging(never inside a discovery container), validated with the samevalidatePluginManifest+ discovery code the runtime uses, listing manifest metadata, every skill name+description, and every MCP command line (rendered against the final install path, incl.PLUGIN_DATAexpansion). Cancelling writes nothing — the preview is stateless; install re-fetches the exact consented SHA and fails loudly if the remote moved.git ls-remotevslockedSha, no fetch, no timers). Applying = temp clone at the new SHA → re-validate → wholesale directory swap (rename-old → promote-new → delete-old, with rollback) → bumplockedSha→ recycle that plugin's running MCP servers via the newMCPServerManager.stopServersWithKeyPrefix(content can change behind an unchanged stdio command line, so the config-signature check cannot notice). Local edits to a managed plugin dir are discarded on update (documented).plugin:<instanceId>:*keys from every local workspace's MCP overrides (reinstall re-attaches the same instanceId, so stale overrides would silently re-enable servers).~/.mux/plugin-data/<instanceId>is preserved behind an "also delete stored plugin data" checkbox, unchecked by default.Settings: Plugins,Install Agent Plugin…,Check for Plugin Updates,Update All Plugins— keyboard rule). No agent-facing installer tool.owner/repo/sub/path[@ref]parses and thesubpathfield is persisted in the source descriptor, but installs reject with "monorepo subpath installs land in v2". Claude Code plugin/marketplace repos fail with a clear message naming the limitation (source stays a discriminated union so an import adapter is additive).Implementation
src/common/config/schemas/agentPluginInstalls.ts(entry + tagged-union source +plugins.jsonfile schema); name grammar shared with the manifest validator viasrc/common/utils/agentPluginName.ts.discoverAgentPluginAt(public single-root wrapper over the existing per-entry discovery, so staged clones get the exact runtime validation);normalizeRepoUrlForCloneextracted tosrc/node/utils/gitUrls.ts(shared with the project clone flow);sourceInput.tsgrammar;AgentPluginInstallService(preview/install/list/uninstall/checkUpdates/update, mutations serialized on an internal queue, staging under~/.mux/plugin-stagingwith stale-dir reclamation,GIT_TERMINAL_PROMPT=0+ SSH BatchMode so private repos without auth fail fast instead of hanging);plugins.*oRPC namespace returningResultvalues;MCPServerManager.stopServersWithKeyPrefixrecycle hook. Backend gating mirrors the MCP provider: the service is constructed withisEnabled: () => experimentsService.isExperimentEnabled(AGENT_PLUGINS).PluginsSettingsSection(list withunmanaged/missing/update available/tag moved/pinnedbadges, two-phase add flow, inline uninstall confirm), experiment-gated section registration + redirect + palette entry.docs/config/mcp-servers.mdx+docs/agents/agent-skills.mdx; Storybook stories with play assertions (consent preview, update states, unchecked-by-default checkbox); unit tests for the input grammar, registry round-trip/self-heal, and the full service lifecycle against real local git remotes (hermetic — local-path remotes exercise the same clone/ls-remote plumbing).Validation
make static-checkgreen (typecheck, ESLint, prettier, docs links); targeted suites: 393 tests across the touched areas (agentPlugins, config, schemas, SettingsPage, palette sources, MCPServerManager, oRPC router, projectService) all pass;test-storybookpasses for the new stories.make dev-server-sandboxinstance (screenshots in the workspace transcript): enabled the experiment via Settings → Experiments (Plugins section appeared immediately), installed a local fixture repo through the full preview → consent → install flow, verified the on-disk registry entry + plugin tree (no.git), advanced the fixture remote →update availablebadge appeared on "Check for updates" → Update bumpedlockedSha/version/updatedAt, uninstall (checkbox unchecked) removed dir + registry but preservedplugin-data, and the reinstalled plugin's MCP server surfaced in Settings → MCP asplugin · …default-disabled/read-only.Risks
~/.mux/plugins.json;config.jsonload/save is untouched. Malformed registry entries degrade to "unmanaged dir" rather than errors; downgrade-safe because older builds never touch the file.stopServersWithKeyPrefixonly stops matching workspaces' server sets; they restart lazily on next use, same as the idle-timeout path. No behavior change for non-plugin servers.agent-pluginsoff, the service throws, the section/palette entry hide, and no new code paths run.Judgement calls
installre-fetches the exact consented SHA (direct SHA fetch, falling back to branch clone + HEAD verification) rather than keeping the preview clone on disk between preview and confirm — a stateless preview means cancel/crash cannot leave partial state, at the cost of a second shallow clone on confirm..git(plain content snapshot): the registry holds all provenance, updates replace the dir wholesale, and a live checkout would only invite in-place edits that updates discard.plugin.json#namechanged): container-entry names are identity (instanceId → PLUGIN_DATA, workspace overrides), so renames require uninstall/reinstall.Update All Pluginsapplies onlyupdate-availableentries; moved tags stay per-plugin manual (a mutated tag deserves the section's warning, not a bulk apply).Deferred (per §5/§6 of the design)
subpathinstalls (sparse checkout; grammar + schema already in place), content-addressed store + symlinked container entries, dev-mode/local-path installs, unmanaged-dir adoption ("convert to managed"),Pinrow action,bun run debug plugin …CLI +/pluginslash command.archive+sha256 / seed dirs for air-gap, restore-from-lock, catalogs/marketplace (Claude marketplace import adapter only on demonstrated demand).autoUpdateboolean reserved in the schema, unused), agent-facing install tool, Claude Code marketplace compatibility.Generated with
mux• Model:anthropic:claude-fable-5• Thinking:xhigh• Cost:$72.27